from typing import Tuple

import pytest

import reflex as rx
from reflex.components.core.match import Match
from reflex.state import BaseState
from reflex.utils.exceptions import MatchTypeError
from reflex.vars import BaseVar


class MatchState(BaseState):
    """A test state."""

    value: int = 0
    num: int = 5
    string: str = "random string"


def test_match_components():
    """Test matching cases with return values as components."""
    match_case_tuples = (
        (1, rx.text("first value")),
        (2, 3, rx.text("second value")),
        ([1, 2], rx.text("third value")),
        ("random", rx.text("fourth value")),
        ({"foo": "bar"}, rx.text("fifth value")),
        (MatchState.num + 1, rx.text("sixth value")),
        rx.text("default value"),
    )
    match_comp = Match.create(MatchState.value, *match_case_tuples)
    match_dict = match_comp.render()  # type: ignore
    assert match_dict["name"] == "Fragment"

    [match_child] = match_dict["children"]

    assert match_child["name"] == "match"
    assert str(match_child["cond"]) == f"{{{MatchState.get_name()}.value}}"

    match_cases = match_child["match_cases"]
    assert len(match_cases) == 6

    assert match_cases[0][0]._var_name == "1"
    assert match_cases[0][0]._var_type == int
    first_return_value_render = match_cases[0][1].render()
    assert first_return_value_render["name"] == "RadixThemesText"
    assert first_return_value_render["children"][0]["contents"] == "{`first value`}"

    assert match_cases[1][0]._var_name == "2"
    assert match_cases[1][0]._var_type == int
    assert match_cases[1][1]._var_name == "3"
    assert match_cases[1][1]._var_type == int
    second_return_value_render = match_cases[1][2].render()
    assert second_return_value_render["name"] == "RadixThemesText"
    assert second_return_value_render["children"][0]["contents"] == "{`second value`}"

    assert match_cases[2][0]._var_name == "[1, 2]"
    assert match_cases[2][0]._var_type == list
    third_return_value_render = match_cases[2][1].render()
    assert third_return_value_render["name"] == "RadixThemesText"
    assert third_return_value_render["children"][0]["contents"] == "{`third value`}"

    assert match_cases[3][0]._var_name == "random"
    assert match_cases[3][0]._var_type == str
    fourth_return_value_render = match_cases[3][1].render()
    assert fourth_return_value_render["name"] == "RadixThemesText"
    assert fourth_return_value_render["children"][0]["contents"] == "{`fourth value`}"

    assert match_cases[4][0]._var_name == '{"foo": "bar"}'
    assert match_cases[4][0]._var_type == dict
    fifth_return_value_render = match_cases[4][1].render()
    assert fifth_return_value_render["name"] == "RadixThemesText"
    assert fifth_return_value_render["children"][0]["contents"] == "{`fifth value`}"

    assert match_cases[5][0]._var_name == f"(({MatchState.get_name()}.num) + (1))"
    assert match_cases[5][0]._var_type == int
    fifth_return_value_render = match_cases[5][1].render()
    assert fifth_return_value_render["name"] == "RadixThemesText"
    assert fifth_return_value_render["children"][0]["contents"] == "{`sixth value`}"

    default = match_child["default"].render()

    assert default["name"] == "RadixThemesText"
    assert default["children"][0]["contents"] == "{`default value`}"


@pytest.mark.parametrize(
    "cases, expected",
    [
        (
            (
                (1, "first"),
                (2, 3, "second value"),
                ([1, 2], "third-value"),
                ("random", "fourth_value"),
                ({"foo": "bar"}, "fifth value"),
                (MatchState.num + 1, "sixth value"),
                (f"{MatchState.value} - string", MatchState.string),
                (MatchState.string, f"{MatchState.value} - string"),
                "default value",
            ),
            f"(() => {{ switch (JSON.stringify({MatchState.get_name()}.value)) {{case JSON.stringify(1):  return (`first`);  break;case JSON.stringify(2): case JSON.stringify(3):  return "
            "(`second value`);  break;case JSON.stringify([1, 2]):  return (`third-value`);  break;case JSON.stringify(`random`):  "
            'return (`fourth_value`);  break;case JSON.stringify({"foo": "bar"}):  return (`fifth value`);  '
            f"break;case JSON.stringify((({MatchState.get_name()}.num) + (1))):  return (`sixth value`);  break;case JSON.stringify(`${{{MatchState.get_name()}.value}} - string`):  "
            f"return ({MatchState.get_name()}.string);  break;case JSON.stringify({MatchState.get_name()}.string):  return (`${{{MatchState.get_name()}.value}} - string`);  break;default:  "
            "return (`default value`);  break;};})()",
        ),
        (
            (
                (1, "first"),
                (2, 3, "second value"),
                ([1, 2], "third-value"),
                ("random", "fourth_value"),
                ({"foo": "bar"}, "fifth value"),
                (MatchState.num + 1, "sixth value"),
                (f"{MatchState.value} - string", MatchState.string),
                (MatchState.string, f"{MatchState.value} - string"),
                MatchState.string,
            ),
            f"(() => {{ switch (JSON.stringify({MatchState.get_name()}.value)) {{case JSON.stringify(1):  return (`first`);  break;case JSON.stringify(2): case JSON.stringify(3):  return "
            "(`second value`);  break;case JSON.stringify([1, 2]):  return (`third-value`);  break;case JSON.stringify(`random`):  "
            'return (`fourth_value`);  break;case JSON.stringify({"foo": "bar"}):  return (`fifth value`);  '
            f"break;case JSON.stringify((({MatchState.get_name()}.num) + (1))):  return (`sixth value`);  break;case JSON.stringify(`${{{MatchState.get_name()}.value}} - string`):  "
            f"return ({MatchState.get_name()}.string);  break;case JSON.stringify({MatchState.get_name()}.string):  return (`${{{MatchState.get_name()}.value}} - string`);  break;default:  "
            f"return ({MatchState.get_name()}.string);  break;}};}})()",
        ),
    ],
)
def test_match_vars(cases, expected):
    """Test matching cases with return values as Vars.

    Args:
        cases: The match cases.
        expected: The expected var full name.
    """
    match_comp = Match.create(MatchState.value, *cases)
    assert isinstance(match_comp, BaseVar)
    assert match_comp._var_full_name == expected


def test_match_on_component_without_default():
    """Test that matching cases with return values as components returns a Fragment
    as the default case if not provided.
    """
    from reflex.components.base.fragment import Fragment

    match_case_tuples = (
        (1, rx.text("first value")),
        (2, 3, rx.text("second value")),
    )

    match_comp = Match.create(MatchState.value, *match_case_tuples)
    default = match_comp.render()["children"][0]["default"]  # type: ignore

    assert isinstance(default, Fragment)


def test_match_on_var_no_default():
    """Test that an error is thrown when cases with return Values as Var do not have a default case."""
    match_case_tuples = (
        (1, "red"),
        (2, 3, "blue"),
        ([1, 2], "green"),
    )

    with pytest.raises(
        ValueError,
        match="For cases with return types as Vars, a default case must be provided",
    ):
        Match.create(MatchState.value, *match_case_tuples)


@pytest.mark.parametrize(
    "match_case",
    [
        (
            (1, "red"),
            (2, 3, "blue"),
            "black",
            ([1, 2], "green"),
        ),
        (
            (1, rx.text("first value")),
            (2, 3, rx.text("second value")),
            ([1, 2], rx.text("third value")),
            rx.text("default value"),
            ("random", rx.text("fourth value")),
            ({"foo": "bar"}, rx.text("fifth value")),
            (MatchState.num + 1, rx.text("sixth value")),
        ),
    ],
)
def test_match_default_not_last_arg(match_case):
    """Test that an error is thrown when the default case is not the last arg.

    Args:
        match_case: The cases to match.
    """
    with pytest.raises(
        ValueError,
        match="rx.match should have tuples of cases and a default case as the last argument.",
    ):
        Match.create(MatchState.value, *match_case)


@pytest.mark.parametrize(
    "match_case",
    [
        (
            (1, "red"),
            (2, 3, "blue"),
            ("green",),
            "black",
        ),
        (
            (1, rx.text("first value")),
            (2, 3, rx.text("second value")),
            ([1, 2],),
            rx.text("default value"),
        ),
    ],
)
def test_match_case_tuple_elements(match_case):
    """Test that a match has at least 2 elements(a condition and a return value).

    Args:
        match_case: The cases to match.
    """
    with pytest.raises(
        ValueError,
        match="A case tuple should have at least a match case element and a return value.",
    ):
        Match.create(MatchState.value, *match_case)


@pytest.mark.parametrize(
    "cases, error_msg",
    [
        (
            (
                (1, rx.text("first value")),
                (2, 3, rx.text("second value")),
                ([1, 2], rx.text("third value")),
                ("random", "red"),
                ({"foo": "bar"}, "green"),
                (MatchState.num + 1, "black"),
                rx.text("default value"),
            ),
            "Match cases should have the same return types. Case 3 with return value `red` of type "
            "<class 'reflex.vars.BaseVar'> is not <class 'reflex.components.component.BaseComponent'>",
        ),
        (
            (
                ("random", "red"),
                ({"foo": "bar"}, "green"),
                (MatchState.num + 1, "black"),
                (1, rx.text("first value")),
                (2, 3, rx.text("second value")),
                ([1, 2], rx.text("third value")),
                rx.text("default value"),
            ),
            "Match cases should have the same return types. Case 3 with return value `<RadixThemesText as={`p`}> {`first value`} </RadixThemesText>` "
            "of type <class 'reflex.components.radix.themes.typography.text.Text'> is not <class 'reflex.vars.BaseVar'>",
        ),
    ],
)
def test_match_different_return_types(cases: Tuple, error_msg: str):
    """Test that an error is thrown when the return values are of different types.

    Args:
        cases: The match cases.
        error_msg: Expected error message.
    """
    with pytest.raises(MatchTypeError, match=error_msg):
        Match.create(MatchState.value, *cases)


@pytest.mark.parametrize(
    "match_case",
    [
        (
            (1, "red"),
            (2, 3, "blue"),
            ([1, 2], "green"),
            "black",
            "white",
        ),
        (
            (1, rx.text("first value")),
            (2, 3, rx.text("second value")),
            ([1, 2], rx.text("third value")),
            ("random", rx.text("fourth value")),
            ({"foo": "bar"}, rx.text("fifth value")),
            (MatchState.num + 1, rx.text("sixth value")),
            rx.text("default value"),
            rx.text("another default value"),
        ),
    ],
)
def test_match_multiple_default_cases(match_case):
    """Test that there is only one default case.

    Args:
        match_case: the cases to match.
    """
    with pytest.raises(ValueError, match="rx.match can only have one default case."):
        Match.create(MatchState.value, *match_case)


def test_match_no_cond():
    with pytest.raises(ValueError):
        _ = Match.create(None)
